########################################################################
#  Searx-Qt - Lightweight desktop application for Searx.
#  Copyright (C) 2020-2022  CYBERDEViL
#
#  This file is part of Searx-Qt.
#
#  Searx-Qt is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Searx-Qt is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################

from PyQt5.QtCore import QSettings, QStandardPaths, QFile

from searxqt.models.instances import InstancesModelTypes

from searxqt import PROFILES_PATH, SETTINGS_PATH
from searxqt.core import log
from searxqt.version import __version__


class ProfileItem:
    def __init__(
        self,
        id_='',
        name='',
        type_=InstancesModelTypes.NotDefined,
        preset=None
    ):
        """
        @param id_: Unique profile id
        @type id: str

        @param name: Profile name
        @type name: str

        @param type_: Profile type
        @type type_: InstancesModelTypes.*

        @param preset: This is only used for new profiles, it is the key of the
                       defaults preset that should be loaded.
        @type preset: None or str
        """
        self.id = id_
        self.name = name
        self.type = type_
        self.preset = preset

    def serialize(self):
        return {
            'id': self.id,
            'name': self.name,
            'type': self.type
        }

    def deserialize(self, data):
        self.id = data['id']
        self.name = data['name']
        self.type = int(data['type'])


class Profiles:
    def __init__(self):
        self._currentProfile = ProfileItem()
        self._profiles = {
            # list with searxqt.models.profiles.ProfileItem's
            'profiles': [],
            # str: profile id
            'default': None
        }

    def __contains__(self, profile):
        return bool(profile in self._profiles['profiles'])

    def __len__(self):
        return len(self._profiles['profiles'])

    def __getitem__(self, index):
        return self._profiles['profiles'][index]

    def add(self, profile):
        """ Add profile to this object. This will not save profiles.conf!
        @param profile: ProfileItem to add.
        @type profile: ProfileItem
        """
        self._profiles['profiles'].append(profile)

    def remove(self, profile):
        """ Remove profile from this object. This will not save profiles.conf!
        @param profile: ProfileItem to remove.
        @type profile: ProfileItem
        """
        self._profiles['profiles'].remove(profile)

    def current(self):
        """ Returns current profile

        @rtype: ProfileItem
        """
        return self._currentProfile

    def default(self):
        """ Returns the default profile

        @rtype: ProfileItem or None when not set.
        """
        return self._profiles['default']

    def setDefault(self, profile):
        """
        @param profile: ProfileItem to set as default.
        @type profile: ProfileItem or None to unset.
        """
        self._profiles['default'] = profile

    def loadProfiles(self, settings):
        """ Load profiles.conf
        """
        self._profiles['default'] = None
        default = settings.value('default', '', str)
        if default != '':
            self._profiles['default'] = ProfileItem()
            self._profiles['default'].deserialize(default)

        # Deserialize profiles
        self._profiles['profiles'].clear()
        for profile in settings.value('profiles', list(), list):
            item = ProfileItem(profile['id'], profile['name'], profile['type'])
            self._profiles['profiles'].append(item)

    def saveProfiles(self):
        settings = self.settings()
        if self._profiles['default'] is not None:
            settings.setValue('default', self._profiles['default'].serialize())
        else:
            settings.setValue('default', '')

        # Store searx-qt version (for backward compatibility)
        settings.setValue('version', __version__)

        # Serialize profiles
        profiles = []
        for profile in self._profiles['profiles']:
            profiles.append(profile.serialize())
        settings.setValue('profiles', profiles)

    def getActiveProfiles(self, settings):
        """ Returns a list with active profile id's
        """
        return settings.value('active', list(), list)

    def profileActive(self, profile):
        """ Check if profile is active or not. This will re-read the
        profiles.conf file.

        @rtype: bool
        """
        settings = self.settings()
        return bool(profile.id in self.getActiveProfiles(settings))

    def profileExists(self, profile):
        """ Check if profile is still present in profiles.conf. This will
        re-read the profiles.conf file. It however won't store the read
        data in this object.

        @rtype: bool
        """
        settings = self.settings()
        profiles = settings.value('profiles', list(), list)
        for p in profiles:
            if p['id'] == profile.id:
             return True
        return False

    def settings(self):
        return QSettings(SETTINGS_PATH, 'profiles')

    def releaseAll(self):
        """ Release active profiles. This may be wanted after a crash of
        searx-qt or the system. User must make sure to close all other
        instances of searx-qt first.
        """
        settings = self.settings()
        settings.remove('active')

    def setProfile(self, settings, profile):
        """ Sets current profile active (also stores it to profiles.conf).
        @type settings: QSettings
        @type profile: ProfileItem
        """
        # Read a list of active profile id's
        activeProfiles = self.getActiveProfiles(settings)

        if self._currentProfile.id in activeProfiles:
            # Remove old profile id from active profiles
            activeProfiles.remove(self._currentProfile.id)

        # Append current profile id to the active profiles list
        if profile.id:
            activeProfiles.append(profile.id)

        # Store the changes
        settings.setValue('active', activeProfiles)

        self._currentProfile = profile

    def removeProfileFiles(self, profileIds):
        """ Removes all profile files for given profile ID's.
        @param profileIds: List with unique profile ID's
        @type profileIds: list
        """
        for profileId in profileIds:
            self.removeProfileFile(profileId)

    def removeProfileFile(self, profileId):
        """ Remove profile file for given profile ID.
        @param profileId: Unique profile id
        @type profileId: str
        """
        # Locate full file-path
        confFilePath = QStandardPaths.locate(
            QStandardPaths.ConfigLocation,
            f"{PROFILES_PATH}{profileId}.conf",
            QStandardPaths.LocateFile
        )

        # confFilePath may be a empty string, this means the file wasn't
        # found. This can happen when the profile has never loaded and saved.
        if confFilePath:
            log.debug(f"Trying to remove: {confFilePath}", self)
            confFile = QFile(confFilePath)
            if confFile.remove():
                log.debug(f"Removed {confFilePath}", self)
                return True
            else:
                log.error(f"Could not remove {confFilePath}", self)

        return False

    def names(self):
        """ Returns all profile names in a list.
        @rtype: list
        """
        return [profile.name for profile in self._profiles['profiles']]
